# graphviz dot language lexer+parser to use with packcc parser generator
%prefix "gv"

%source{

#include <stdio.h>

static const char *dbg_str[] = { "Evaluating rule", "Matched rule", "Abandoning rule" };

#define PCC_DEBUG(event, rule, level, pos, buffer, length) \
    fprintf(stdout, "%*s%s %s @%d [%.*s]\n", (int)(level * 2), "", dbg_str[event], rule, (int)pos, (int)length,  buffer); fflush(stdout)

    /* NOTE: To guarantee the output order, stderr, which can lead a race condition with stdout, is not used. */

}


start	<-  graph

graph	<-  _  "strict"? _ ("graph" / "digraph") _ ID? _ "{" children:stmt_list "}" _ 

stmt_list	<-  _ (stmt _ ';'? _ stmt_list? )?

stmt	<-  ID '=' ID
  / attr_stmt
  / edge_stmt
  / subgraph
  / node_stmt


attr_stmt	<-  ( 'graph' / 'node' / 'edge' ) attr_list

attr_list	<-  _ '[' a_list? ']' _ attr_list?

a_list	<-  _ ID (_ '=' _ ID )? _ ','? a_list?

edge_stmt	<-  (subgraph / node_id) edgeRHS attr_list?

edgeRHS	<-  _ ('->' / '--') _ (subgraph / node_id) _ edgeRHS?

node_stmt	<-  node_id attr_list?

node_id	<-  ID port?

port	<-  ':' ID (':' compass_pt )?
  / ':' compass_pt

subgraph	<-  ('subgraph' _ ID? _  )? '{' stmt_list '}'
  / 'subgraph' _ ID

compass_pt	<-  'n'/'ne'/'e'/'se'/'s'/'sw'/'w'/'nw'

ID	<-  STRING
  / NUMBER
  / QUOTED_STRING
  / HTML_STRING

STRING	<-  ([a-zA-Z_][a-zA-Z0-9_]*)

NUMBER	<-  ("-"? ("." [0-9]+ / [0-9]+("." [0-9]*)?))

HTML_STRING	<-  html_raw_string

html_raw_string	<-  '<' (html_char / html_raw_string)* '>'

html_char	<-  (!('>' / '<') .)*

QUOTED_STRING	<-  '"' '"'
  / ('"' chars ("\\" NEWLINE chars)? '"') (_ '+' _ QUOTED_STRING )? 

chars	<-  char+

char	<-  [^"\\\0-\x1F\x7f]
  / '\\"'
  / '\\' NEWLINE
  / '\\'


COMMENT	<-  (BLOCK_COMMENT / C_COMMENT / MACRO_COMMENT)

BLOCK_COMMENT	<-  "/*" (!"*/" .  )* "*/"

C_COMMENT	<-  "//" (![\n] .)*

MACRO_COMMENT	<-  "#" (![\n] .  )* [\n]?


_ <-  (WHITESPACE / COMMENT)*

NEWLINE	<-  [\n\r]+

WHITESPACE	<-  ([ \t / ('\'\n )] / NEWLINE)+


%%

int main() {
    gv_context_t *ctx=gv_create(NULL);
    while (gv_parse(ctx, NULL)){;}
    gv_destroy(ctx);
    return 0;
}

